package mmc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import mmc.action.skill.Skill;
import mmc.cell.ClientCell;
import mmc.cell.ClientCellBunch;
import mmc.cell.ServerCell;
import mmc.gl.BackgroundLoader;
//import mmc.networking.CreateObject;
import mmc.networking.UpdateMessage;
import mmc.networking.data.AddUpdate;
import mmc.networking.data.ChunkUnload;
import mmc.networking.data.ChunkUpdate;
import mmc.networking.data.ObjectUpdate;
import mmc.networking.data.RemoveUpdate;
import mmc.networking.data.Update;
import mmc.objects.Mark;
import mmc.objects.Mob;
import mmc.objects.RemotePlayer;
import mmc.physics.PhysicsEngine;
import mmc.ui.Text;
import mmc.util.APSCounter;

import org.lwjgl.LWJGLException;

public class Scene{
	public Player player;
	private ClientCellBunch cell;
	private RemoteServer context;
	private ServerCell[] initFrom;
	private EngineObject spawnPoint;
	private boolean initialized = false;
	private PhysicsEngine physics;
	public int remotePlayerId;
	private APSCounter updatesPerSecond = new APSCounter("Ps");
	private APSCounter packetsPerSecond = new APSCounter("Us");
	
	private List<Light> lights = new Vector<Light>();
	public Map<Integer,EngineObject> objects = new HashMap<Integer,EngineObject>();
	private List<EngineObject> objectsToAdd = new Vector<EngineObject>();
	private List<EngineObject> objectsToRemove = new Vector<EngineObject>();
	private BackgroundLoader backgroundLoader;
	
	public List<UpdateMessage> updates = Collections.synchronizedList(new LinkedList<UpdateMessage>());
	
	/*
	 * comment by nermin and rainer mod2
	 * 
	 */
	
	public Scene(RemoteServer context, ServerCell[] initFrom, EngineObject spawnPoint){
		this.initFrom = initFrom;
		this.context = context;
		this.spawnPoint = spawnPoint;
		physics = new PhysicsEngine(null);
	}
	
	public void init(){
		if(Constants.CELL_BACKGROUND_LOADING)
			backgroundLoader = new BackgroundLoader(Client.client.createSharedDrawable());
		ClientCellBunch c = new ClientCellBunch(backgroundLoader);
		for(ServerCell cell : initFrom)
			c.putCell(new ClientCell(cell));
		
		setCellBunch(c);
		
		initFrom = null;
		
		try {
			player = new Player(this, spawnPoint.x, spawnPoint.y, spawnPoint.z);
		} catch (LWJGLException e) {
			throw new RuntimeException("Exception in Scene initialization: "+e.getMessage(),e);
		}

		//addObject(new mmc.objects.Sun());
		
		initialized = true;
	}
	
	public void setCellBunch(ClientCellBunch cell){this.cell = cell;}
	
	public synchronized void render(EngineObject observer){
		if(!initialized)
			return;
		
		player.look();
		
		for(Light l : lights){
			l.updateLightPosition();
		}
		
		if(!Constants.CELL_BACKGROUND_LOADING)
			cell.updateBuffers();
		cell.render(player);
		
		for(EngineObject o : objects.values()){
			o.render(player);
		}		
	}
	
	public Player getPlayer(){
		return player;
	}

	private void processAdds(){
		synchronized(objectsToAdd){
			for(EngineObject o : objectsToAdd){
				objects.put(o.id, o);
				if(o instanceof Light)
					lights.add((Light) o);
				
				o.initOnClient(this);
			}
			
			objectsToAdd.clear();
		}
	}
	
	private void processRemovals(){
		synchronized(objectsToRemove){
			for(EngineObject o : objectsToRemove){
				objects.remove(o.id);
				if(o instanceof Light){
					lights.remove((Light) o);
					//glDisable(((Light) o).lightId);
				}
				o.dispose();
			}
			
			objectsToRemove.clear();
		}
	}
	
	public synchronized void update(float dt) {
		if(!initialized)
			init();
		
		float minTS = 5e-3f;
		

		Skill.update(dt);
		processAdds();
		processRemovals();
		
		float dtl = dt;
		while((dt = (dtl>minTS?minTS:dtl)) > 0){
			dtl -= dt;
			player.update(dt);
			
			for(EngineObject o : objects.values()){
				o.update(dt);
				
				if(o instanceof MovableEngineObject)
					physics.updateClient((MovableEngineObject) o, dt);
			}
		}
		
		checkForServerUpdate();
	}
	
	private void checkForServerUpdate() {
		while(updates.size()>0){
			updatesPerSecond.notify(0,1);
			UpdateMessage ul = updates.remove(0);
			packetsPerSecond.notify(0,ul.updates.length);
		
			for(Update u : ul.updates){
				if(u instanceof AddUpdate){
					AddUpdate au = (AddUpdate) u;
					
					EngineObject o = au.o;
					addObject(o);
					if(o instanceof RemotePlayer){
						RemotePlayer rp = (RemotePlayer) o;
						if(rp.id == remotePlayerId)
							player.setRemotePlayer(rp);
					}
					
					processAdds();
				}else if(u instanceof RemoveUpdate){
					RemoveUpdate ru = (RemoveUpdate) u;
					EngineObject o = objects.get(ru.id);
					
					if(o==null){
						throw new RuntimeException("Bad object id in remove");
					}
					
					removeObject(o);
					
				}else if(u instanceof ChunkUpdate){
					ChunkUpdate cu = (ChunkUpdate) u;
					
					cell.putCell(new ClientCell(cu.cell));
				}else if(u instanceof ChunkUnload){
					ChunkUnload cu = (ChunkUnload) u;
					
					cell.unloadCell(cu.pos);
				}else if(u instanceof ObjectUpdate){
					ObjectUpdate o = (ObjectUpdate) u;
					
					EngineObject local = objects.get(o.o.id);
					if(local==null){
						throw new RuntimeException("Bad object id in objectupdate: "+o.o);
					}		
					
					if(o.o instanceof RemotePlayer && o.o.id == player.getRemotePlayer().id){
						if(o.o.health != local.health)
							Text.addMessage(-(local.health-o.o.health)+"");						
					}
					
					if((o.o instanceof Mob | o.o instanceof RemotePlayer) && player.target != null)			//Refresh target
						if(player.target.id == o.o.id)
							player.target = o.o;
					
					if(!o.o.copyTo(local))
					{
						objects.put(o.o.id, o.o);						
					}
				}else{
					throw new RuntimeException("Unknown Update type "+u.getClass().getCanonicalName());
				}
			}
		}
	}
	
	public void addObject(EngineObject o){
		objectsToAdd.add(o);
	}
	
	public void removeObject(EngineObject o){
		objectsToRemove.add(o);
	}
	
	public void addUpdate(UpdateMessage update){
		updates.add(update);
	}

	public RemoteServer getContext() {
		return context;
	}

	public ArrayList<float[]> getNearPlayers()
	{
		ArrayList<float[]> nearPlayers = new ArrayList<float[]>();
		
		for(EngineObject o : objects.values())
		{
			if(o instanceof RemotePlayer | o instanceof Mark | o instanceof Mob)
			{
				if( player.getRemotePlayer() != o)				
				{						
					if(player.inRange(o, player.viewDistance))
					{
						float[] p = {o.x, o.z, (o instanceof Mob)?2f:(o instanceof Mark)?1f:0f};
						nearPlayers.add(p);
					}
				}
			}
		}		
		return nearPlayers;
	}
	
	public EngineObject getNextEnemy()
	{
		List<Integer> keys = new ArrayList<Integer>(objects.keySet());
		EngineObject o = null;
		
		int i = -1;
		if(player.target != null)
			i = keys.indexOf(player.target.id);
		
		while(o==null)
		{
			i++;
			if(i >= objects.size())
				break;
			
			EngineObject help = objects.get(keys.get(i));
			if((help instanceof Mob && !((Mob)help).dead)) // | (help instanceof RemotePlayer && player.getRemotePlayer() != help))
				o = help;
		}
		return o;
	}
	
	public EngineObject getNextFriend()
	{
		List<Integer> keys = new ArrayList<Integer>(objects.keySet());
		EngineObject o = null;
		
		int i = -1;
		if(player.target != null)
			i = keys.indexOf(player.target.id);
		boolean once = true;
		while(o==null)
		{
			if(!once)
				break;
			
			i++;
			if(i >= objects.size()) {
				i= 0;
				once = false;
			}
				//break;
			
			EngineObject help = objects.get(keys.get(i));
			if((help instanceof RemotePlayer) && player.getRemotePlayer() != help) // && player.getRemotePlayer() != help))
				o = help;
		}
		return o;
	}
	
	public void notifyMsgArrived(String msg){
		Client.client.ui.chat.receiveMessage(msg);
	}
	
	public ClientCellBunch getCellBunch(){
		return cell;
	}
	
	public BackgroundLoader getBackgroundLoader(){
		return backgroundLoader;
	}

	public APSCounter getUpdatesPerSecond() {
		return updatesPerSecond;
	}

	public APSCounter getPacketsPerSecond() {
		return packetsPerSecond;
	}
	
	public void shutdown(){
		if(Constants.CELL_BACKGROUND_LOADING){
			if(backgroundLoader!=null)
				backgroundLoader.quit();
		}
	}
}
